package cn.coisini.security.filter;

import cn.coisini.model.common.constants.RedisConstants;
import cn.coisini.model.common.enums.ResultEnum;
import cn.coisini.utils.ExceptionUtil;
import cn.coisini.utils.IpUtils;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.jetbrains.annotations.NotNull;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.util.concurrent.TimeUnit;

/**
 * @author xiangshaw
 * Description: 令牌桶限流
 */
@Log4j2
@Component
@RequiredArgsConstructor
public class RateLimitingFilter extends OncePerRequestFilter {

    // 默认每个 IP 的最大请求数
    private static final long DEFAULT_LIMIT = 100;
    // 令牌桶的过期时间（单位：秒）
    private static final long DEFAULT_EXPIRY_TIME_SECONDS = 60;
    // redis模板
    private final StringRedisTemplate stringRedisTemplate;

    /**
     * 检查 IP 是否超过限流限制
     * @param ip 当前请求的 IP 地址
     * @return true 表示超出限流，false 表示未超出
     */
    public boolean rateLimit(String ip) {
        // 构建 Redis 键
        String key = RedisConstants.IP_RATE_LIMITER_KEY + ip;
        // 递增访问数
        Long visits = stringRedisTemplate.opsForValue().increment(key);
        // 如果访问计数为 null 或者是第一次访问，则设置过期时间
        if (visits == null || visits == 1) {
            // 获取配置的过期时间
            long expiryTime = getExpiryTimeFromConfig();
            stringRedisTemplate.expire(key, expiryTime, TimeUnit.SECONDS);
        }
        // 获取配置的请求限制
        long limit = getLimitFromConfig();
        // 判断是否超出限制
        return visits != null && visits > limit;
    }

    /**
     * 从 Redis 配置中获取 IP 限流的 QPS 阈值
     * @return 返回配置的阈值，如果未配置则返回默认值
     */
    private long getLimitFromConfig() {
        Object configValue = stringRedisTemplate.opsForHash().get(RedisConstants.REDIS_CONFIG_PREFIX, RedisConstants.IP_QPS_THRESHOLD_KEY);
        if (configValue != null) {
            try {
                // 解析配置值
                return Long.parseLong(configValue.toString());
            } catch (NumberFormatException e) {
                log.warn("阈值格式错误，使用默认值: {}", DEFAULT_LIMIT);
            }
        } else {
            log.warn("未配置 IP 请求限制 QPS 阈值，使用默认值: {}", DEFAULT_LIMIT);
        }
        // 返回默认值
        return DEFAULT_LIMIT;
    }

    /**
     * 从 Redis 配置中获取令牌桶的过期时间
     * @return 返回配置的过期时间，如果未配置则返回默认值
     */
    private long getExpiryTimeFromConfig() {
        Object expiryValue = stringRedisTemplate.opsForHash().get(RedisConstants.REDIS_CONFIG_PREFIX, RedisConstants.IP_RATE_LIMIT_EXPIRY_KEY);
        if (expiryValue != null) {
            try {
                // 解析配置值
                return Long.parseLong(expiryValue.toString());
            } catch (NumberFormatException e) {
                log.warn("过期时间格式错误，使用默认值: {}", DEFAULT_EXPIRY_TIME_SECONDS);
            }
        } else {
            log.warn("未配置 IP 限流过期时间，使用默认值: {}", DEFAULT_EXPIRY_TIME_SECONDS);
        }
        // 返回默认值
        return DEFAULT_EXPIRY_TIME_SECONDS;
    }

    /**
     * IP 限流过滤器的核心逻辑
     */
    @Override
    protected void doFilterInternal(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response,
                                    @NotNull FilterChain filterChain) throws ServletException, IOException {
        // 获取请求 IP
        String ip = IpUtils.getIp(request);
        // 检查是否超出限流
        if (rateLimit(ip)) {
            // 处理限流异常
            ExceptionUtil.handleException(response, ResultEnum.ACCESS_LIMIT);
            return;
        }
        filterChain.doFilter(request, response);
    }
}
